/**
 * 
 */
package craneding.gwt.databean.client.ui.field;

import java.util.Date;

import com.google.gwt.core.client.GWT;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FocusListener;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.ImageBundle;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

import craneding.gwt.databean.client.model.FieldInfo;

/**
 * a calendar widget
 * <h3>CSS Style Rules</h3>
 * <ul class='css'>
 * <li>.DataBean-ImageFieldInput{ primary style }</li>
 * <li>.DataBean-CalendarFieldInput .gwt-TextBox{ primary style }</li>
 * <li>.DataBean-CalendarFieldInput .icon{ primary style }</li>
 * <li>.CalendarTimeWidget{ primary style }</li>
 * <li>.CalendarTimeWidget .month-selector{ primary style }</li>
 * <li>.CalendarTimeWidget .month-selector .month-backward{ primary style }</li>
 * <li>.CalendarTimeWidget .month-selector .month-year{ primary style }</li>
 * <li>.CalendarTimeWidget .month-selector .month-forward{ primary style }</li>
 * <li>.CalendarTimeWidget .calendar-view{ primary style }</li>
 * <li>.CalendarTimeWidget .calendar-view .day-title{ primary style }</li>
 * <li>.CalendarTimeWidget .calendar-view .date-cell{ primary style }</li>
 * <li>.CalendarTimeWidget .calendar-view .date-cell :HOVER{ primary style }</li>
 * <li>.CalendarTimeWidget .calendar-view .filler-cell{ primary style }</li>
 * <li>.CalendarTimeWidget .calendar-view .weekend{ primary style }</li>
 * </ul>
 * @author crane.ding
 * @version 1.0 2008-12-3
 */
public class CalendarFieldInput extends Composite implements IFieldInput,
		KeyboardListener, FocusListener {

	private TextBox box = new TextBox();
	private Date time = new Date();
	private FieldInfo fieldInfo;
	private String fieldName;
	private boolean isOnKeyUp = true;// 是否需要在ONKEYUP事件中校验.
	private ValidationPopupPanel validateMsg;
	private CalendarTimeWidget calendarWidget;

	static interface CalendarImage extends ImageBundle {
		AbstractImagePrototype CalendarIcon();
	}

	/**
	 * 日期时间组件
	 * 
	 * @author crane.ding
	 * @version 1.0 2008-12-3
	 */
	public class CalendarTimeWidget extends PopupPanel {

		public final static int YEAR = 1;
		public final static int MONTH = 2;
		public final static int DATE = 5;
		private Timer timer;

		public CalendarTimeWidget() {
			super(true);
			sinkEvents(Event.ONMOUSEOUT);
		}

		@Override
		public void onBrowserEvent(Event event) {
			super.onBrowserEvent(event);
			int type = DOM.eventGetType(event);
			if (type == Event.ONMOUSEOUT)
				if (!DOM.isOrHasChild(getElement(), DOM.eventGetToElement(event))) {
					if (timer == null) {
						timer = new Timer() {
							@Override
							public void run() {
								hide();
							}
						};
						timer.schedule(1000);
					} else
						timer.schedule(1000);
				}
		}

		private VerticalPanel panel;

		private Widget getMonthSelectorWidget() {
			Label monthBackward = new Label("«");// last month
			Label monthForward = new Label("»");// next month
			// select year's month
			final Label monthYaer = new Label(get(YEAR) + "年" + get(MONTH)
					+ "月");

			monthBackward.setTitle("上个月");
			monthForward.setTitle("下个月");

			DockPanel dockPanel = new DockPanel();
			dockPanel.add(monthBackward, DockPanel.WEST);
			dockPanel.add(monthYaer, DockPanel.CENTER);
			dockPanel.add(monthForward, DockPanel.EAST);

			dockPanel.setStyleName("month-selector");
			monthBackward.setStyleName("month-backward");
			monthForward.setStyleName("month-forward");
			monthYaer.setStyleName("month-year");

			monthBackward.addClickListener(new ClickListener() {
				public void onClick(Widget sender) {
					add(MONTH, -1);
					monthYaer.setText(get(YEAR) + "年" + get(MONTH) + "月");
					reload();
				}
			});
			monthForward.addClickListener(new ClickListener() {
				public void onClick(Widget sender) {
					add(MONTH, +1);
					monthYaer.setText(get(YEAR) + "年" + get(MONTH) + "月");
					reload();
				}
			});
			return dockPanel;
		}

		@SuppressWarnings("deprecation")
		private Widget getCalendarView() {
			FlexTable grid = new FlexTable();
			grid.setCellPadding(3);
			grid.setCellSpacing(0);
			String[] month = { "日", "一", "二", "三", "四", "五", "六" };
			for (int i = 0; i < 7; i++) {
				grid.setText(0, i, month[i]);
				grid.getFlexCellFormatter().setStyleName(0, i, "day-title");
			}
			Date format = new Date(time.getTime());
			format.setDate(1);// set the day of month is 1.
			int day = format.getDay();// get the day of the week.
			int minimum = getMinimum(DATE);// get the min day of the month.
			int maximum = getMaximum(DATE);// get the max day of the month.
			for (int i = 1; i < 7; i++) {
				for (int j = 0; j < 7; j++) {
					// if before min day or after max day set empty
					if ((i == 1 && j < day) || minimum > maximum) {
						grid.setText(i, j, "-");
						grid.getFlexCellFormatter().setStyleName(i, j,
								"filler-cell");
					} else {
						final int index = minimum;
						Label dayUI = new Label(minimum++ + "");
						dayUI.addClickListener(new ClickListener() {
							public void onClick(Widget sender) {
								String date = DateTimeFormat.getFormat(
										"yyyy-MM-").format(time);
								if (index < 10)
									date = date.concat("0" + index);
								else
									date = date.concat(index + "");
								time = DateTimeFormat.getFormat("yyyy-MM-dd")
										.parse(date);
								box.setText(date);
								CalendarTimeWidget.this.hide();
							}
						});
						grid.setWidget(i, j, dayUI);
						grid.getFlexCellFormatter().setStyleName(i, j,
								"date-cell");
					}
					if (j == 0 || j == 6)
						grid.getFlexCellFormatter().addStyleName(i, j,
								"weekend");
				}
			}
			grid.setStyleName("calendar-view");
			return grid;
		}

		private void add(int field, int amount) {
			String str = DateTimeFormat.getFormat("yyyy-MM-dd").format(time);
			String[] split = str.split("-");
			int year = Integer.valueOf(split[0]);
			int month = Integer.valueOf(split[1]);
			int date = 1;
			if (field == MONTH) {
				if (month + amount <= 0) {
					year--;
					month = 12;
				} else
					month += amount;
			} else if (field == YEAR) {
				year += amount;
			}
			str = year + "-" + month + "-" + date;
			time = DateTimeFormat.getFormat("yyyy-MM-dd").parse(str);
		}

		private int get(int field) {
			String str = null;
			if (field == DATE)
				str = DateTimeFormat.getFormat("dd").format(time);
			else if (field == MONTH)
				str = DateTimeFormat.getFormat("MM").format(time);
			else if (field == YEAR)
				str = DateTimeFormat.getFormat("yyyy").format(time);
			return str == null ? 0 : Integer.valueOf(str);
		}

		private int getMaximum(int field) {
			if (field == DATE) {
				String str = DateTimeFormat.getFormat("yyyy-MM").format(time);
				String[] split = str.split("-");
				int year = Integer.valueOf(split[0]);
				int month = Integer.valueOf(split[1]);
				switch (month) {
				case 2:
					if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
						return 29; // leap year
					else
						return 28;
				case 4:
				case 6:
				case 9:
				case 11:
					return 30;
				default:
					return 31;
				}
			} else if (field == MONTH) {
				return 12;
			}
			return 0;
		}

		private int getMinimum(int field) {
			if (field == DATE || field == MONTH) {
				return 1;
			} else if (field == YEAR) {
				return 1900;
			}
			return 0;
		}

		private void reload() {
			panel.clear();
			panel.add(getMonthSelectorWidget());
			panel.add(getCalendarView());
		}

		@Override
		public void show() {
			if (!matches(box.getText().trim())) {
				time = new Date();
			} else {
				time = DateTimeFormat.getFormat("yyyy-MM-dd").parse(
						box.getText().trim());
			}
			if (panel == null) {
				panel = new VerticalPanel();
				setWidget(panel);
				panel.setStyleName("CalendarTimeWidget");
			}
			reload();
			super.show();
		}

		@Override
		public void hide() {
			super.hide();
			if (timer != null)
				timer.cancel();
		}
	}

	public CalendarFieldInput() {
		box.addKeyboardListener(this);
		box.addFocusListener(this);
		HorizontalPanel inner = new HorizontalPanel();
		inner.add(box);
		CalendarImage image = GWT.create(CalendarImage.class);
		Image icon = image.CalendarIcon().createImage();
		icon.setStyleName("icon");
		icon.addClickListener(new ClickListener() {
			public void onClick(Widget sender) {
				if (calendarWidget == null)
					calendarWidget = new CalendarTimeWidget();
				int left = CalendarFieldInput.this.getAbsoluteLeft();
				int top = CalendarFieldInput.this.getAbsoluteTop();
				int height = CalendarFieldInput.this.getOffsetHeight();
				if ((top + height) > Window.getClientHeight())
					top -= height;
				else
					top += height;
				calendarWidget.setPopupPosition(left, top);
				calendarWidget.show();
			}
		});
		inner.add(icon);
		inner.setCellVerticalAlignment(icon, VerticalPanel.ALIGN_MIDDLE);
		initWidget(inner);
		setStyleName("DataBean-CalendarFieldInput");
	}

	public Object getValue() {
		String text = box.getText().trim();
		if (text.length() <= 0)
			return null;
		if (fieldInfo.datatype() == Date.class)
			return DateTimeFormat.getFormat("yyyy-MM-dd").parse(text);
		return text;
	}

	public void setFieldInfo(String fieldName, FieldInfo fieldInfo) {
		this.fieldName = fieldName;
		this.fieldInfo = fieldInfo;

		setMetaData();
	}

	private void setMetaData() {
		box.setName(this.fieldName);
		String description = this.fieldInfo.description();
		if (description.length() > 0)
			setTitle(description);
	}

	public void setValue(Object value) {
		if (value == null) {
			time = new Date();
			box.setText("");
			return;
		}
		if (value instanceof Date) {
			time = (Date) value;
			String date = DateTimeFormat.getFormat("yyyy-MM-dd").format(time);
			box.setText(date);
		} else {
			time = DateTimeFormat.getFormat("yyyy-MM-dd").parse(
					value.toString());
			box.setText(value.toString());
		}
	}

	public void onKeyDown(Widget sender, char keyCode, int modifiers) {
	}

	public void onKeyPress(Widget sender, char keyCode, int modifiers) {
		isOnKeyUp = false;
		validateOnPress(keyCode);
	}

	private void validateOnPress(char keyCode) {
		String regex = "0123456789-";
		if (regex.indexOf(keyCode) == -1) {
			DOM.eventPreventDefault(Event.getCurrentEvent());
			return;
		}

		int cursorPos = box.getCursorPos();
		StringBuffer str = new StringBuffer(box.getText().trim());
		str.insert(cursorPos, keyCode);
		String text = str.toString();

		if (keyCode == '-') {
			if (text.length() != 4 || text.length() != 7) {
				DOM.eventPreventDefault(Event.getCurrentEvent());
				return;
			}
		}

		String[] split = text.split("-");
		if (split == null || split.length <= 0 || text.indexOf("--") != -1) {
			DOM.eventPreventDefault(Event.getCurrentEvent());
			return;
		}
		for (int i = 0; i < split.length; i++) {
			switch (i) {
			case 0:
				regex = "([1-2])|(19[0-9]{0,2})|(19[0-9]{2}-{0,1})|(20[0-9]{0,2}-{0,1})|(20[0-9]{2}-{0,1})";
				if (!split[i].matches(regex)) {
					DOM.eventPreventDefault(Event.getCurrentEvent());
					return;
				}
				break;
			case 1:
				regex = "(0[1-9]{0,1})|(1[0-2]{0,1})";
				if (!split[i].matches(regex)) {
					DOM.eventPreventDefault(Event.getCurrentEvent());
					return;
				}
				break;
			case 2:
				regex = "(0[1-9]{0,1})|([1-2][0-9]{0,1})|(3[0-1]{0,1})";
				if (!split[i].matches(regex)) {
					DOM.eventPreventDefault(Event.getCurrentEvent());
					return;
				}
				break;

			default:
				break;
			}
		}
	}

	private boolean validateOnUp() {
		String regex = "";
		String text = box.getText().trim();
		String[] split = text.split("-");
		if (split == null || split.length <= 0 || text.indexOf("--") != -1) {
			return false;
		}
		for (int i = 0; i < split.length; i++) {
			switch (i) {
			case 0:
				regex = "([1-2])|(19[0-9]{0,2})|(19[0-9]{2}-{0,1})|(20[0-9]{0,2}-{0,1})|(20[0-9]{2}-{0,1})";
				if (!split[i].matches(regex)) {
					return false;
				}
				break;
			case 1:
				regex = "(0[1-9]{0,1})|(1[0-2]{0,1})";
				if (!split[i].matches(regex)) {
					return false;
				}
				break;
			case 2:
				regex = "(0[1-9]{0,1})|([1-2][0-9]{0,1})|(3[0-1]{0,1})";
				if (!split[i].matches(regex)) {
					return false;
				}
				break;
			default:
				break;
			}
		}
		return true;
	}

	private native boolean matches(String text)/*-{
	var reDate = /(?:19|20\d{2})-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])/;
	return reDate.test(text);
	}-*/;

	public void onKeyUp(Widget sender, char keyCode, int modifiers) {
		if (isOnKeyUp && !validateOnUp())
			box.setText("");
		int length = box.getText().trim().length();
		if (!isOnKeyUp && (length == 4 || length == 7)
				&& keyCode != KEY_BACKSPACE && keyCode != KEY_DELETE)
			box.setText(box.getText().trim() + '-');
		isOnKeyUp = true;// 设置需要在ONKEYUP事件中校验.
	}

	public void onFocus(Widget sender) {
		if (validateMsg == null || validateMsg.isHide())
			return;
		// 错误则选中文本内容
		box.setSelectionRange(0, box.getText().length());
		validateMsg.hide();
	}

	public void onLostFocus(Widget sender) {
		String text = box.getText().trim();

		String msg = FieldInputUtil.validateByFieldInfo(fieldInfo, text);
		if (msg != null) {
			showValidationMsg(msg);
		}

		if (text.length() > 0 && !matches(text)) {
			showValidationMsg(fieldInfo.label() + "格式错误.");
			return;
		}
	}

	private void showValidationMsg(String msg) {
		if (validateMsg == null)
			validateMsg = new ValidationPopupPanel();
		validateMsg.show(this, msg);
	}

	public void validityCheck() throws ValidationException {
		String text = box.getText().trim();

		String msg = FieldInputUtil.validateByFieldInfo(fieldInfo, text);
		if (msg != null) {
			throw new ValidationException(msg);
		}

		if (text.length() > 0 && !matches(text)) {
			throw new ValidationException(fieldInfo.label() + "格式错误.");
		}
	}
}
